package com.wdb.common.repository.support;

import com.wdb.common.base.BaseEntity;
import com.wdb.common.constant.Constant;
import com.wdb.common.repository.CrudRepository;
import com.wdb.common.repository.Mappable;
import com.wdb.common.sequence.IdWorker;
import com.wdb.common.util.BaseEntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * @param <M> {@link Mappable}
 * @param <E> {@link BaseEntity}
 * @author long.shiyu
 */
public abstract class CrudRepositorySupport<M extends Mappable<E>, E extends BaseEntity> implements CrudRepository<E> {

    protected static final Logger LOGGER = LoggerFactory.getLogger(CrudRepository.class);

    protected Class<?> entityClass;
    @Autowired
    protected M mapper;

    public CrudRepositorySupport() {
        //get subclass
        Class<? extends CrudRepository> clz = this.getClass();
        //get superclass and cast to parameterized type
        ParameterizedType parameterizedType = (ParameterizedType) clz.getGenericSuperclass();
        //get actual types
        Type[] types = parameterizedType.getActualTypeArguments();
        entityClass = (Class<?>) types[1];
        if (LOGGER.isDebugEnabled()) {
            LOGGER.info("cache domain class {}", entityClass.getName());
        }
    }

    @Override
    public E add(E entity) {
        BaseEntityUtils.fillCreateInfo(entity);
        if (Objects.isNull(entity.getId())) {
            entity.setId(IdWorker.getId());
        }
        mapper.insertSelective(entity);
        return entity;
    }

    @Override
    public E find(Long id) {
        E entity = mapper.selectByPrimaryKey(id);
        if (Objects.isNull(entity) || Objects.equals(Constant.Y, entity.getDeleted())) {
            return null;
        }
        return entity;
    }

    @Override
    public void remove(Long id) {
        E entity = mapper.selectByPrimaryKey(id);
        if (Objects.nonNull(entity) && Objects.equals(Constant.N, entity.getDeleted())) {
            entity.setDeleted(Constant.Y);
            BaseEntityUtils.fillUpdateInfo(entity);
            mapper.updateByPrimaryKeySelective(entity);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    @Transactional(rollbackFor = Exception.class)
    public void batchRemove(List<Long> ids) {
        if (!CollectionUtils.isEmpty(ids)) {
            ids.forEach(id -> {
                try {
                    if (Objects.nonNull(id)) {
                        E e = (E) entityClass.newInstance();
                        e.setId(id);
                        e.setDeleted(Constant.Y);
                        BaseEntityUtils.fillUpdateInfo(e);
                        mapper.updateByPrimaryKeySelective(e);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.error("Exception occurred on batch remove entities,cause:{},message:{}", e.getCause(),
                            e.getMessage());
                    }
                }
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public E modify(E entity) {
        E e = Optional.of(entity).get();

        Long id = e.getId();

        if (Objects.isNull(id)) {
            throw new NullPointerException("modify entity id can not be null");
        }
        BaseEntityUtils.fillUpdateInfo(e);
        if (mapper.updateByPrimaryKeySelective(e) > 0) {
            return mapper.selectByPrimaryKey(id);
        }

        throw new RuntimeException("modify failed");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<E> batchAdd(List<E> entities) {
        if (CollectionUtils.isEmpty(entities)) {
            return new ArrayList<>();
        }
        entities.forEach(entity -> {
            if(Objects.isNull(entity.getId())){
                entity.setId(IdWorker.getId());
            }
            BaseEntityUtils.fillCreateInfo(entity);
        });
        mapper.insertList(entities);
        return entities;
    }
}
